using System.Collections.Generic;
using System.Linq;
using FluentNHibernate.Mapping;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Impl;
using NHibernate.Linq;
using NHibernateInsight.Tests.Domain;
using NHibernateInsight.Tests.Infrastructure;
using NUnit.Framework;
using NHibernateInsight.Tests.Insights.Linq.Extensions;
using Order=NHibernateInsight.Tests.Domain.Order;

namespace NHibernateInsight.Tests.Insights.Criteria
{
    [TestFixture]
    public class Using_Criteria_query_with_aliases : TestContext
    {
        private class Mappings_of
        {
            public class Product : ClassMap<Domain.Product>
            {
                public Product()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    Map(x => x.Name);
                    HasManyToMany(x => x.Prospects)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsBag();
                }
            }
            public class Prospect : ClassMap<Domain.Prospect>
            {
                public Prospect()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    Map(x => x.Initials);
                    HasManyToMany(x => x.Products)
                        .Cascade.SaveUpdate()
                        .AsBag();
                    HasManyToMany(x => x.ProductsSet)
                        .Cascade.SaveUpdate()
                        .Table("ProductsToProspects")
                        .AsSet();
                    HasMany(x => x.Orders)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsBag();
                    HasMany(x => x.OrdersSet)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsSet();
                    HasMany(x => x.Addresses)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsBag();
                    HasMany(x => x.AddressesSet)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsSet();
                }
            }
            public class Order : ClassMap<Domain.Order>
            {
                public Order()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    References(x => x.Prospect)
                        .Cascade.SaveUpdate();
                }
            }
            public class Address : ClassMap<Domain.Address>
            {
                public Address()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    References(x => x.Prospect)
                        .Cascade.SaveUpdate();
                }
            }

        }

        [SetUp]
        public void Given_that()
        {
            PrepareForSql2005ExpressDb();


            HideCreateAndDropDbSqlFromConsole();
            HideCreateAndDropSchemaSqlFromConsole();

            // Prepare data!
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>()
                          .Add<Mappings_of.Order>()
                          .Add<Mappings_of.Address>());

            var product1 = new Product() { Name = "P1" };
            var product2 = new Product() { Name = "P1" };
            var product3 = new Product() { Name = "P2" };
            var order1 = new Order();
            var order2 = new Order();
            var order3 = new Order();
            var order4 = new Order();
            var order5 = new Order();
            var address1 = new Address();
            var address2 = new Address();
            var prospect = new Prospect() { Initials = "Initials" };

            prospect.RelateTo(product1);
            prospect.RelateTo(product2);
            prospect.RelateTo(product3);
            prospect.RelateTo(order1);
            prospect.RelateTo(order2);
            prospect.RelateTo(order3);
            prospect.RelateTo(order4);
            prospect.RelateTo(order5);
            prospect.RelateTo(address1);
            prospect.RelateTo(address2);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }

            CurrentSession.Evict(product1);
            CurrentSession.Evict(product2);
            CurrentSession.Evict(product3);
            CurrentSession.Evict(order1);
            CurrentSession.Evict(order2);
            CurrentSession.Evict(order3);
            CurrentSession.Evict(order4);
            CurrentSession.Evict(order5);
            CurrentSession.Evict(address1);
            CurrentSession.Evict(address2);
            CurrentSession.Evict(prospect);
        }

        [TearDown]
        public void Cleanup_database()
        {
            Cleanup();
        }

        /// <summary>
        /// Then it will brake with an exception that it cannot find the property
        /// </summary>
        [Test]
        public void When_executing_a_query_which_has_restriction_over_many_to_one_without_having_an_alias_for_it()
        {
            var crit = CurrentSession.CreateCriteria<Order>();
            crit.Add(Restrictions.Eq("Prospect.Initials", "Initials"));

            crit.List();
        }

        /// <summary>
        /// Then it will execute a query with inner join
        /// </summary>
        [Test]
        public void When_executing_a_query_which_has_restriction_over_many_to_one_first_level_and_having_an_alias_for_it()
        {
            var crit = CurrentSession.CreateCriteria<Order>();
            crit.CreateAlias("Prospect", "p");
            crit.Add(Restrictions.Eq("p.Initials", "Initials"));

            crit.List();
        }

        /// <summary>
        /// Then it will brake with an exception that it cannot find the property
        /// </summary>
        [Test]
        public void When_executing_a_query_which_has_restriction_over_many_to_one_and_further_over_many_to_many_without_having_an_alias_for_it()
        {
            var crit = CurrentSession.CreateCriteria<Order>();
            crit.CreateAlias("Prospect", "p");
            crit.Add(Restrictions.Eq("p.Products.Name", "p1"));

            crit.List();
        }

        /// <summary>
        /// Then it will brake with an SQL exception
        /// </summary>
        [Test]
        public void When_executing_a_query_which_has_restriction_over_many_to_one_and_further_over_many_to_many_and_having_an_alias_ONLY_for_the_complete_association_path()
        {
            var crit = CurrentSession.CreateCriteria<Order>();
            crit.CreateAlias("Prospect.Products", "pro");
            crit.Add(Restrictions.Eq("pro.Name", "p1"));

            crit.List();
        }

        /// <summary>
        /// Then it will execute the query with the right joins
        /// </summary>
        [Test]
        public void When_executing_a_query_which_has_restriction_over_many_to_one_and_further_over_many_to_many_and_having_an_all_aliases_for_the_each_part_of_association_path()
        {
            var crit = CurrentSession.CreateCriteria<Order>();
            crit.CreateAlias("Prospect", "pro");
            crit.CreateAlias("pro.Product", "prd");
            crit.Add(Restrictions.Eq("prd.Name", "p1"));

            crit.List();
        }
    }
}